module CPUDefs where

import Data.Int
import Data.Word
import Data.STRef
import Data.Array.ST
import Control.Monad.ST
import Control.Monad.Reader
import GHC.Arr


data CPU = CPU {
   aReg     :: Operand,
   xReg     :: Operand,
   yReg     :: Operand,
   sp       :: Word8,
   pc       :: Address,
   status   :: Status
}

instance Show CPU where
    show cpu = " A " ++ show (aReg cpu)
            ++ " | X " ++ show (xReg cpu)
            ++ " | Y " ++ show (yReg cpu)
            ++ " | PC " ++ show (pc cpu)
            ++ " | SP " ++ show (sp cpu)

data Status = Status {
   c  :: Bool,
   z  :: Bool,
   i  :: Bool,
   d  :: Bool,
   b  :: Bool,
   v  :: Bool,
   n  :: Bool
} deriving Show

type NES s a = ReaderT (STRef s (Memory s),STRef s CPU) (ST s) a

type MemArray s = STUArray s Address Operand
data Memory s = Memory {
    low     :: STRef s (MemArray s),
    ppu     :: STRef s (MemArray s),
    high    :: STRef s (MemArray s)
}

-- additional type declarations
type Operand    = Int8
type OPCode     = Int8
type Address    = Word16
data Index      = X | Y

-- Before = X, After = Y (apparently)
data Indexed = Before | After

data AddrMode = 
     ZeroPage (Maybe Index) Operand
   | Absolute (Maybe Index) Address
   | Relative Operand
   | Indirect Address
   | Indexed Indexed Operand

-- Interface to change memory (IArray interface is nicer that MArray)
(!) :: STRef s (MemArray s) -> Address -> NES s Operand
ref ! addr = lift $ readSTRef ref >>= flip readArray addr

(//) :: STRef s (MemArray s) -> (Address,Operand) -> NES s ()
ref // (addr,op) = lift $ readSTRef ref >>= \arr -> writeArray arr addr op

-- Operating on CPU as if it were in the state monad
get :: NES s CPU
get = asks snd >>= lift . readSTRef

gets :: (CPU -> a) -> NES s a
gets f = do
   cpuref <- asks snd
   cpu    <- lift $ readSTRef cpuref
   return $ f cpu

modify :: (CPU -> CPU) -> NES s ()
modify f = do
    cpuref <- asks snd
    lift $ modifySTRef cpuref f

put :: CPU -> NES s ()
put cpu = do
    ref <- asks snd
    lift $ writeSTRef ref cpu

baseSP :: Address
baseSP = 0x0100

